﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MainController.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the MainController type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace ScrumForTeamSystem.Setup.SfTSConfigatorUI
{
    using System;
    using System.Collections.Generic;

    using ScrumForTeamSystem.Setup.SfTSConfigatorUI.Properties;

    using SfTSDeployment;

    /// <summary>
    /// Initializes instance of MainController
    /// </summary>
    internal class MainController
    {
        /// <summary>
        /// The ui element.
        /// </summary>
        private readonly IMainUI mainUi;

        /// <summary>
        /// The meta data file path property prefix.
        /// </summary>
        private const string MetaDataFileProperty = "METADATAFILEPATH=";

        /// <summary>
        /// The zip file path property prefix.
        /// </summary>
        private const string ZipfileProperty = "ZIPFILEPATH=";

        /// <summary>
        /// The tfs url property prefix
        /// </summary>
        private const string TfsUrlProperty = "TEAMPROJECTCOLLECTIONURL=";

        /// <summary>
        /// The service end point path.
        /// </summary>
        private const string ServiceEndPoint = "/ScrumForTeamSystem/3.0/WorkItemChangedEndPoint.asmx";

        /// <summary>
        /// The project collections dictionary.
        /// </summary>
        private readonly Dictionary<string, ProjectCollectionInfo> projectCollections = new Dictionary<string, ProjectCollectionInfo>();

        /// <summary>
        /// Initializes a new instance of the <see cref="MainController"/> class.
        /// </summary>
        /// <param name="args">The args array.</param>
        /// <param name="mainUi">The main UI.</param>
        public MainController(IEnumerable<string> args, IMainUI mainUi)
        {
            this.mainUi = mainUi;

            this.ApplyCommandLineArguments(args);

            this.mainUi.ListCollections += this.OnListCollections;
            this.mainUi.SelectedCollectionChanged += this.OnSelectedCollectionChanged;
            this.mainUi.Deploy += this.OnDeployTemplate;
            this.mainUi.Subscribe += this.OnSubscribe;
            this.mainUi.Retract += this.OnRetract;
            this.mainUi.Unsubscribe += this.OnUnsubscribe;

            this.mainUi.SetStatus("Ready");
        }

        /// <summary>
        /// Occurs when [collections loaded].
        /// </summary>
        public event EventHandler CollectionDetailsLoaded;

        /// <summary>
        /// Called when [unsubscribe].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnUnsubscribe(object sender, EventArgs e)
        {
            string endPoint;
            ProjectCollectionInfo collection;

            if (!this.TryGetActiveCollection(this.mainUi.SelectedCollectionName, out collection)
                || !this.TryGetSubscriptionEndPoint(out endPoint))
            {
                return;
            }

            var subscriptionId = collection.GetEventSubscriptionId(endPoint, Settings.Default.SubscriptionFilter);

            Action execute = () =>
                {
                    while (subscriptionId != null)
                    {
                        collection.RemoveEventSubscription(subscriptionId.Value);

                        subscriptionId = collection.GetEventSubscriptionId(endPoint, Settings.Default.SubscriptionFilter);
                    }
                };

            Action success = () =>
            {
                this.ListCollections();
                this.mainUi.SetStatus("Subscription(s) removed.");
            };

            Action<Exception> fail = ex =>
                {
                    this.mainUi.SetStatus(ex);
                    this.mainUi.EnableUIInteration(true);
                };

            this.mainUi.EnableUIInteration(false);

            AsyncHelper.ExecuteAsync(execute, success, fail);
        }

        /// <summary>
        /// Called when [retract].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnRetract(object sender, EventArgs e)
        {
            ProjectCollectionInfo collection;
            if (!this.TryGetActiveCollection(this.mainUi.SelectedCollectionName, out collection))
            {
                return;
            }

            var id = collection.GetDeployedTemplateId(this.mainUi.MetaDataFilePath);

            if (!id.HasValue)
            {
                return;
            }

            Action execute = () => collection.RetractProcessTemplate(id.Value);

            Action success = () =>
            {
                this.ListCollections();
                this.mainUi.SetStatus("Template retracted.");
            };

            Action<Exception> fail = ex =>
                {
                    this.mainUi.SetStatus(ex);
                    this.mainUi.EnableUIInteration(true);
                };

            this.mainUi.EnableUIInteration(false);

            AsyncHelper.ExecuteAsync(execute, success, fail);
        }

        /// <summary>
        /// Called when [subscribe].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnSubscribe(object sender, EventArgs e)
        {
            ProjectCollectionInfo collection;
            string endPoint;

            if (!this.TryGetActiveCollection(this.mainUi.SelectedCollectionName, out collection) 
                || !this.TryGetSubscriptionEndPoint(out endPoint)
                || collection.HasEventSubscription(endPoint, Settings.Default.SubscriptionFilter))
            {
                return;
            }

            Action execute =
                () => collection.AddSubscription(endPoint, Settings.Default.SubscriptionFilter);

            Action success = () =>
                {
                    this.ListCollections();
                    this.mainUi.SetStatus("Subscription added.");
                };

            Action<Exception> fail = ex =>
                {
                    this.mainUi.SetStatus(ex);
                    this.mainUi.EnableUIInteration(true);
                };

            this.mainUi.EnableUIInteration(false);

            AsyncHelper.ExecuteAsync(execute, success, fail);
        }

        /// <summary>
        /// Called when [deploy template].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnDeployTemplate(object sender, EventArgs e)
        {
            ProjectCollectionInfo collection;
            if (!this.TryGetActiveCollection(this.mainUi.SelectedCollectionName, out collection))
            {
                return;
            }

            Action execute =
                () => collection.DeployProcessTemplate(this.mainUi.MetaDataFilePath, this.mainUi.ZipFilePath);

            Action success = () =>
                {
                    this.ListCollections();
                    this.mainUi.SetStatus("Template deployed.");
                };

            Action<Exception> fail = ex =>
                {
                    this.mainUi.SetStatus(ex);
                    this.mainUi.EnableUIInteration(true);
                };

            this.mainUi.EnableUIInteration(false);

            AsyncHelper.ExecuteAsync(execute, success, fail);
        }

        /// <summary>
        /// Called when [selected collection changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnSelectedCollectionChanged(object sender, EventArgs e)
        {
            this.LoadSelectedCollectionDetails();
        }

        /// <summary>
        /// Called when [load collections].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnListCollections(object sender, EventArgs e)
        {
            this.ListCollections();
        }

        /// <summary>
        /// Loads the collection details.
        /// </summary>
        private void LoadSelectedCollectionDetails()
        {
            ProjectCollectionInfo collection;
            if (!this.TryGetActiveCollection(this.mainUi.SelectedCollectionName, out collection))
            {
                this.mainUi.CanDeploy(false);
                this.mainUi.CanRetract(false);
                this.mainUi.CanSubscribe(false);
                this.mainUi.CanUnsubscribe(false);

                this.mainUi.SetSubscriptionList(null);
                this.mainUi.SetTemplateList(null);

                return;
            }

            Func<IEnumerable<SubscriptionInfo>> loadSubscription = () => (IEnumerable<SubscriptionInfo>)collection.ListSubscription();
            AsyncHelper.LoadAsync(this.mainUi, loadSubscription, this.mainUi.SetSubscriptionList, null);

            Func<IEnumerable<TemplateInfo>> loadTemplates = () => (IEnumerable<TemplateInfo>)collection.GetListOfInstalledTemplates();
            AsyncHelper.LoadAsync(this.mainUi, loadTemplates, this.mainUi.SetTemplateList, null);

            try
            {
                var isTemplateDeployed = collection.IsTemplateDeployed(this.mainUi.MetaDataFilePath);
                string endPoint;
                var canSubscribe =
                    this.TryGetSubscriptionEndPoint(out endPoint)
                    && !collection.HasEventSubscription(endPoint, Settings.Default.SubscriptionFilter);

                this.mainUi.CanDeploy(!isTemplateDeployed);
                this.mainUi.CanRetract(isTemplateDeployed);
                this.mainUi.CanSubscribe(canSubscribe);
                this.mainUi.CanUnsubscribe(!canSubscribe);

                if (this.CollectionDetailsLoaded != null)
                {
                    this.CollectionDetailsLoaded(this, EventArgs.Empty);
                }
            }
            catch (Exception ex)
            {
                this.mainUi.SetStatus(ex);
            }
        }

        /// <summary>
        /// Lists the collections.
        /// </summary>
        private void ListCollections()
        {
            this.projectCollections.Clear();

            ProjectCollectionHandler projectCollectionHandler = null;

            Func<IEnumerable<ProjectCollectionInfo>> loadItems = () =>
                {
                    try
                    {
                        projectCollectionHandler = new ProjectCollectionHandler(this.mainUi.TfsUrl);
                    }
                    catch (Exception ex)
                    {
                        this.mainUi.SetStatus(ex);
                        return null;
                    }

                    foreach (var projectCollection in projectCollectionHandler.ListCollections())
                    {
                        this.projectCollections.Add(projectCollection.Name, projectCollection);
                    }

                    return this.projectCollections.Values;
                };

            Action callback = () =>
                {
                    if (projectCollectionHandler != null)
                    {
                        this.mainUi.TfsUrl = projectCollectionHandler.TfsUri.AbsoluteUri;
                    }

                    this.LoadSelectedCollectionDetails();
                };

            AsyncHelper.LoadAsync(this.mainUi, loadItems, this.mainUi.SetCollectionList, callback);
        }

        /// <summary>
        /// Applies the file paths.
        /// </summary>
        /// <param name="args">The args array.</param>
        private void ApplyCommandLineArguments(IEnumerable<string> args)
        {
            foreach (var arg in args)
            {
                if (arg.StartsWith(MetaDataFileProperty))
                {
                    this.mainUi.MetaDataFilePath = arg.Substring(MetaDataFileProperty.Length);
                }

                if (arg.StartsWith(ZipfileProperty))
                {
                    this.mainUi.ZipFilePath = arg.Substring(ZipfileProperty.Length);
                }

                if (arg.StartsWith(TfsUrlProperty))
                {
                    this.mainUi.TfsUrl = arg.Substring(TfsUrlProperty.Length);
                }
            }

            if (string.IsNullOrEmpty(this.mainUi.MetaDataFilePath))
            {
                string metaDataFilePath;
                this.mainUi.MetaDataFilePath = 
                    FileLocationHelper.TryGetDefaultMetaFilePath(out metaDataFilePath)
                           ? metaDataFilePath
                           : "[Select Meta Data File]";
            }

            if (string.IsNullOrEmpty(this.mainUi.ZipFilePath))
            {
                string zipFilePath;
                this.mainUi.ZipFilePath = 
                    FileLocationHelper.TryGetDefaultZipFilePath(out zipFilePath)
                           ? zipFilePath
                           : "[Select Template Zip File]";
            }
        }

        /// <summary>
        /// Tries the get subscription end point.
        /// </summary>
        /// <param name="endPoint">The end point.</param>
        /// <returns><c>True</c> if the end point is created; otherwise false.</returns>
        private bool TryGetSubscriptionEndPoint(out string endPoint)
        {
            Uri tfsUri;
            var tfsUrl = this.mainUi.TfsUrl;

            endPoint = !string.IsNullOrEmpty(tfsUrl) && Uri.TryCreate(tfsUrl, UriKind.Absolute, out tfsUri)
                ? string.Format("{0}://{1}:{2}{3}", tfsUri.Scheme, tfsUri.Host, tfsUri.Port, ServiceEndPoint)
                : null;

            return !string.IsNullOrEmpty(endPoint);
        }

        /// <summary>
        /// Tries to get the active collection.
        /// </summary>
        /// <param name="collectionName">Name of the collection.</param>
        /// <param name="collection">The collection.</param>
        /// <returns><c>True</c> if an active collection is found; otherwise <c>false</c>.</returns>
        private bool TryGetActiveCollection(string collectionName, out ProjectCollectionInfo collection)
        {
            collection = null;

            return !string.IsNullOrEmpty(collectionName) && this.projectCollections.TryGetValue(collectionName, out collection);
        }
    }
}
